Chapter 14: Inheritance and concept of namespace
From book Python Programming (Problem solving, Packages and Libraries) published by McGraw Hill Education (India) Private limited.
By:
Note the following:-
Markdown and code cells (extension .ipynb) and then downloaded as html. If someone wants to "modify" or "extend' this document, you may ask for the original .ipynb file by sending me an e-mail at:- 999.anuraggupta@gmail.com14.2. Basics of inheritance in Python
14.2.1. Introduction to subclassing (Inheritance)
Suppose you have a class named BaseClass, then you can derive another class say DerivedClass from it.
class DerivedClass(BaseClass):#
This is shown in the following example:
class BaseClass:
pass
# Derived class
class DerivedClass(BaseClass):# DerivedClass is derived from BaseClass
pass
# Create objects b (Of type BaseClass) and d (of type DerivedClass)
b = BaseClass()
d = DerivedClass()
print('Type of b ', type(b))
print('Type of d ', type(d))
print('Parent of d',DerivedClass.__bases__)#.__bases__ -> parent of derived class
print('Parent of b ',BaseClass.__bases__)# All classes inherit from class object
14.3. Single inheritance
14.3.1. A simple inheritance of all functionalities of the base class or parent class.
The simplest case would be when the derived class simply inherits all the functionalities of the base class without adding any functionality of its own. Even though it is the simplest case, it may not be very useful. This will be clear from following example. The following script creates a Pet class and then inherits a Dog class from the Pet class.
class Pet: # Parent class
def __init__(self, pName= 'No name'):
self.pName = pName
class Dog(Pet): # Derived class. Has no __init__() of its own
pass
p = Pet('my Pet') #Create an instance of Pet
d = Dog('Tommy') #Create an instance of Dog
print(p.pName,type(p)) # Output is my Pet <class '__main__.Pet'>
print(d.pName, type(d)) # Output is Tommy <class '__main__.Dog'>
The following script illustrates the concept:-
The script is given on page 345 of the book
class Animal: # Base class
def speak(self):
print('Animal-> Animal sounds')
def walk(self):
print('Animal-> I can walk')
class Dog(Animal): # Derived class Dog
def speak(self):
print('Dog-> Bark!')
class Cat(Animal): # Derived class Cat
def speak(self):
print("Cat-> Meow!")
# Create objects a (Of type Animal), d (Of type Dog) and c (Of type Cat)
a = Animal() # Create object of type Animal
d=Dog() # Create object of type Dog
c=Cat() # Create object of type Cat
a.speak() # Prints Animal-> Animal sounds
d.speak() # Prints Dog-> Bark!
c.speak() # Prints Cat-> Meow!
a.walk() # Prints Animal-> I can walk
d.walk() # Prints Animal-> I can walk
c.walk() # Prints Animal-> I can walk
14.3.2. Inheritance, where the derived class has an __init__() method of its own
You can derive a class and write an __init__() method of the derived class. But if you do so, you will override the __init__() method of the base class and the __init__() method of the base class will not be called. This is shown as follows:
class Pet:
def __init__(self, pName= 'No name'):
self.pName = pName
print('__init__() of Pet class called')
class Dog(Pet): # Derived class
def __init__(self, pName= 'Dog'): # __init__() of derived class
self.pName = pName
print('__init__() of Dog Class called')
# Create objects p (Of class Pet) and d (Of class Dog)
p = Pet('my Pet') #Call __init__() of Pet Class
d = Dog('Tommy') #Call __init__() of Dog Class
print(p.pName)
print(d.pName)
Take another example to show how easy it is to inherit from a base class and to create classes and objects which can do much more than the base class. In the above example you had a Pet class and a Dog class. Suppose you want to have the Dog class with colour of Dog as a parameter and also give colour to the Dog object during its creation. You can do this as shown in the following code:
The script is given on page 348 of the book
class Pet:
def __init__(self, pName= 'No name'):
self.pName = pName
print('init() of Pet class called')
class Dog(Pet):
def __init__(self, pName= 'Dog', color = "Black"):
self.pName = pName
self.color = color
print('init() of Dog Class called')
d = Dog('Tommy') #Call init() of Dog Class
print(d.pName, d.color)
d2 = Dog("Muffy", "Brown")
print(d2.pName, d2.color)
14.3.3. Both the derived class and the parent class have their own __init__() methods
In Python there may be a situation where you want to use the __init__() methods of both the derived Class and the Parent class, because you may want some of the initialization to be done in the __init__() method of the derived class and rest of the initialization to be done in the __init__() method of the Base Class. If you want to call the __init__() method of the base class then you have to
__init__() method of the derived class __init__() method of base class using the keyword super().
Here again there is a slight difference in Python 2.x and Python 3.x. The syntax for the two version is shown in the following code:
super(DerivedClass, self).__init__() # In Python 2.x
super().__init__() # In Python 3.x
There are two things to note in the call to __init__() of the base class:
super() does not have a self-parameter in Python 3.x though it does have a self parameter in Python 2.x.This will become clear from the following example:
The script is given on page 349 of the book
class Pet:
def __init__(self, pName= 'No name'):
self.pName = pName
class Dog(Pet):
def __init__(self, pName, sound= 'bark'):
self.sound = sound
#super(Dog, self).__init__(pName) In Python 2.x
super().__init__(pName) # In Python 3.x
# Create object d
d = Dog('Tommy', 'Woff!') #Create an instance of Dog
print('Sound is -> ', d.sound)
print('Name of pet-> ', d.pName)
14.3.4. Use of super() to call methods other than __init__() of base class also
In the above example, the super() method had been used to call the __init__() of the base class. But you can use the super() to call methods other than __init__() of base class also. Suppose you have a method in the base class, you can implement the same method in child class also. If you do so you may override the method of the base class. This is shown in the following code:
The script is given on page 350 of the book
class Pet:
def __init__(self, pName= 'No name'):
self.pName = pName
def walk(self):
print('Pet is walking')
class Dog(Pet):
def __init__(self, pName, sound= 'bark'):
self.sound = sound
#super(Dog, self).__init__(pName) In Python 2.x
super().__init__(pName) # In Python 3.x
def walk(self):
print('Dog is walking')
#super(Dog, self).walk() In Python 2.x
super().walk() # In Python 3.x
# Create object d
d = Dog('Tommy', 'Woff!') #Create an instance of Dog
d.walk()
14.3.5. Calling the __init__() methods of the parent class by using the name of the parent class
It is possible to access the overridden methods of the parent class by using the name of the parent class. This can be done for the __init__() method and also for other methods.
Following is the code where the derived class first calls its own __init__() and then specifically calls the __init__() of the parent class by using the name Pet of the parent class:
The script is given on page 351 of the book
class Pet:
def __init__(self, pName= 'No name'):
self.pName = pName
print('constructor of Pet class called')
class Dog(Pet):
def __init__(self, pName, sound= 'bark'):
print('Constructor of Dog class called')
Pet.__init__(self, pName) #Calling __init__() of Pet class
# Create object d of class Dog
d = Dog('Tommy', 'Woff!') #Create an instance of Dog
print('Name of pet-> ', d.pName)
14.3.6. Abstract methods
The script is given on page 352 of the book
In Python, it is possible to create a class with a method but the method is not implemented in the class.
So to use this method, you must derive a child class from this parent class and then implement the method in the child class.
The question then is why would you need to do this?
The answer can be given by an example: suppose you have a Pet class from which you derive various child classes like say Dog and Cat. You want both the Dog and the Cat class to have a method say speak(), but you don’t want to implement the speak() method in the parent Pet class. This can be implemented by having an abstract speak() method in base class which is implemented in child classes Dog and Cat.
class Pet:
def speak(self): # Abstract method
raise NotImplementedError("Please implement this method")
class Dog(Pet):
def speak(self): # Abstract method implemented in Dog class
print('Dog barks')
class Cat(Pet): # Abstract method not implemented in Cat class
pass
# Create objects d of Dog class and c of Cat class
d = Dog()
d.speak() # output is Dog barks
c = Cat()
c.speak() #Error
14.4. Multiple inheritance
Python provides limited support for multiple inheritance also. The syntax for creating a derived class from three different Base classes is shown in the following pseudo code:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
14.4.1. Potential problem in multiple inheritance
In multiple inheritance there is one potential problem. Suppose a method is defined in more than one parent class, then which of the methods should be implemented? In Python, the rule is depth-first, left-to-right. What does this mean?
Base1 and go all up to its parent, i.e., to the greatest depth. Base1 or any of its parents, it will next look up the next parent to the right which is Base2 and go right up to all its parents. Base3 and so on. Base1 (the entire depth means look up all the parent classes of Base1 as well). If the method is not found in the leftmost Parent or Base class, then it searches the entire depth of the base class to its right and so on. This will become clear from the following example, where you have a GrFather, i.e., GrandFather Class. The GrFather class has two derived classes, i.e., Father and Mother classes. From these you get two child classes Child1 and Child2. The difference between Child1 and Child2 is the order of the parents Father and Mother. This is shown in Figure 14.3 (Not shown here but given in the book).
The script is as follows:
The script is given on page 354 of the book
class GrFather: # GrandFather
def snore(self):
print('Grandfather snoring')
class Father(GrFather): # Father derived class from GrFather
def earn(self):
print('Father earns')
class Mother(GrFather): # Mother derived class from GrFather
def earn(self):
print('Mother earns')
class Child1(Father, Mother): # parent class Father comes before Mother
pass
class Child2(Mother, Father): # parent class Mother comes before Father
pass
# Create objects c1 and c2 of classes Chid1 and Child2
c1 = Child1()
c2 = Child2()
c1.snore() # Call snore() of GrFather class
c1.earn() # Will Call earn() of Father Class
c2.earn() # Will Call earn() of Mother class
14.4.4. Creating custom containers
In Python it is possible to create your own custom containers. For example, you could create a class say Vehicle and then have a vehicle object which in turn could act just like a list like say vehicle[0], vehicle[1], etc.
To do this, Python provides many methods. Two of them are discussed here. They are __getitem__() and __setitem__().
So if a class implements a __setitem__() method, then the object can have index or keys and those keyed items can be set to values. So if a class say Vehicle implements __setitem__(), then you could have objects of Vehicle class with index or keys. The following example will clarify the concept:
The script is given on page 356 of the book
class Vehicle(object):
def __init__(self, totalV):
self.totalV = [None]*totalV
def __setitem__(self, vehicle_number, vehicle_name):
self.totalV[vehicle_number] = vehicle_name
def __getitem__(self, vehicle_number):
return self.totalV[vehicle_number]
# Create object vehicle of class Vehicle
vehicle = Vehicle(3)
# Since Vehicle class implements __setitem__() and __getitem__()
# you can have index for vehicle object
vehicle[0] = 'truck'
vehicle[1] = 'car'
print('vehicle[0]->', vehicle[0], 'vehicle[1]->', vehicle[1])
# Note vehicle[2] also exists but its value is None
print('vehicle[2]->', vehicle[2])
14.5. Concept of namespace
So you can actually think of the name space as a dictionary (let’s call it dictNamespace) as follows:
dictNamespace = {‘varName1’: object1, ‘varName2’: object2,’varName3’: object3}
14.5.1. locals() and globals()
To understand this example you need to understand two globally defined built-in functions namely locals() and globals() available in Python.
The functions work in the following manner: the return values of these functions are dictionaries of all the variables as keys (the keys are returned as strings) and their values as values of the dictionary.
This is shown in the following code snippet on IDLE. Here you create two variables myInt and myList and also one function myFunc() which doesn’t do anything. Now when you use locals(), you get a dictionary (as indicated by two curly braces { and } marking beginning and end).
In this dictionary, the keys are all surrounded by single quotes, i.e., (‘’), so all the keys are strings. The values are actual values. The keys corresponding to variables created by the user (i.e., user defined) and their values are next to them. Each key is separated from its value by a colon as follows:
The script is given on page 358 of the book
# ---ON IDLE---
>>> myInt = 10
>>> myList = ['a', 'b']
>>>def myFunc(): pass
>>> locals()
{'__name__': '__main__', '__package__': None, '__spec__': None, 'myInt': 10, 'myFunc': <function myFunc at 0x02307228>, '__loader__': <class'_frozen_importlib.BuiltinImporter'>, '__builtins__': <module 'builtins' (built-in)>, 'myList': ['a', 'b'], '__doc__': None}
14.5.2. Namespace dictionary __dict__
As explained above, every namespace is a key value pair. The key is the name of the attribute in the namespace and its value is the value of that attribute in that namespace. This dictionary of attributes and their corresponding values can be accessed using the __dict__ attribute of the class or its instance. Please remember that a class has a different namespace than its instance. Suppose you have a class say myClass and an instance of this class say myObject. Then the namespace of myClass.__dict__ is different from the namespace myObject.__dict__
The following script shows this:
The script is given on page 358 of the book
# class_namespace_instance_namespace.py
class Dog:
dog_sound = 'bark'# Class variable common to all instances of Dog
def __init__(self, color):
self.color = color
# Create instance of Dog
blackDog = Dog('black')
# class and instance namespace
print('Class namespace-> ',Dog.__dict__) # Gives Class namespace
print('Instance or object namespace-> ', blackDog.__dict__)# object namespace
# You can add attributes and their values to instance namespace
# A new attribute dog_act with value 'Wag tail' added
blackDog.__dict__['dog_act'] = 'Wag tail'
print("New attribute 'dog_act' with value ->",blackDog.__dict__['dog_act'])
# The class attribute dog_sound modified
blackDog.__dict__['dog_sound'] = 'Loud Bark'
print('Class attribute dog_sound changed->',blackDog.__dict__['dog_sound'])
14.7. Exercise
b. Write a script which does the following:
Solution:- The solution is not given in the book
class Human:
def nationality(self):
raise NotImplementedError("Please implement this method")
class Indian(Human):
def nationality(self):
print("Indian")
# Create object
indian = Indian()
# Call nationality method of subclass
indian.nationality()
14.8.2. __new__() versus __init__() methods
So far it has been said that the __init__() method is the “constructor” of objects. This is not completely true even though it does serve the purpose for most script writing.
Beginning with Python 3.x, there is a method __new__() which is the actual constructor.
The following code shows that __new__() is always called before __init__():-
The script is given on page 362 of the book
class X(object):
def __init__(self):
print('__init__() called')
def __new__(cls):
print('__new__() called')
return super().__new__(cls)
# Create an object
x = X()
Note: If you override the __new__() method and do not call super() on __new__(), the __init__() method will never be executed. This is shown in the following code where the line of code calling super() is “commented out”:
class X(object):
def __init__(self):
print('__init__() called')
def __new__(cls):
print('__new__() called')
# super() commented out
# return super().__new__(cls)
# Create an object
x = X()
14.8.3. Understanding meta-classes in Python
Following aspects of “meta-classes” in Python are relevant:
type” can be used in two different ways:
o First: Use type(some_item) to get the “type of that ” item. (The item could be a function, method, object, class, etc.)
o Second: Use the keyword type in creating a class from the type meta-class.
So when you use a class definition to create a class, you inherit your class from the class at the top of hierarchy which is object . However, when you create a meta-class you don’t inherit your meta-class from object, rather, you inherit your meta-class from type.
So the keyword class can be used to create both classes as well as meta-classes.
This will be clear from the followig script:-# 1. AClass() implicitly inherits from object
class AClass():
pass
# 2. BClass() explicitly inherits from object
class BClass(object):
pass
# 3. CClass() is subclass of AClass()
class CClass(AClass):
pass
# 4. AMeta() is a metaclass (Not an ordinary class)
class AMeta(type):
pass
# 5. BMeta() is a class. It does not inherit from type but from AMeta()
class BMeta(metaclass = AMeta):
pass
# Check type of class and metaclass
print(type(AClass)) # <class 'type'>
print(type(AMeta)) # <class 'type'>
# Create objects
ac = AClass()
print(type(ac)) # <class '__main__.AClass'>
bm = BMeta()
print(type(bm)) # <class '__main__.BMeta'>
print(isinstance(BMeta, AMeta)) # True
Note that there is another way to create a meta-class, i.e.,, by using the keyword __metaclass__. If you define the attribute __metaclass__ = SomeMetaClass, then Python will use that meta-class to create your class. The use is as follows:
The script is given on page 364 of the book
class XMeta(type):
pass
class YMeta:
__metaclass__ = XMeta
a_obj = YMeta()
print(a_obj) # <__main__.YMeta object at 0x004DD970>
print(a_obj.__metaclass__) # <class '__main__.XMeta'>
In Python 3.x, the preferred way is to give a keyword argument pair (keyword is meta-class and the argument is the name of the meta-class from which the class is derived) in the list of base classes. This is shown in the following code:
class XMeta(type):
pass
class YMeta(object, metaclass = XMeta):
pass
14.8.4. “Real subclasses” versus “Virtual subclasses” In Python you can have:
The concept is very simple and can be explained as follows:
ABC, that ABC becomes a “virtual parent” of the subclass. Note that such classes which can become virtual parents of other classes (through registration), are called Abstract Base Classes, or ABCs. The following code shows how a class can become a child class by register() method of ABC class or a class derived from ABC
The script is given on page 365 of the book
from abc import ABCMeta
class FromABC(metaclass = ABCMeta):
pass
class X(object):
def __init__(self):
print('X created')
# Register X as a sub-class of FromABC
FromABC.register(X)
# Create instance of X
x = X()
# Check whether object x is instance of FromABC
print('is x instance of FromABC? ', isinstance(x, FromABC))
print('X.mro()->', X.mro())
14.8.5. Abstract Base Class Module, i.e., abc
Here two items are used from the module “abc”. They are:
abc.ABC @abc.abstractmethod. This decorator is used to declare a method as an abstract method. Declaring a method as abstract method ensures that all inherited classes from this class are forced to implement this function.
The following code shows how to create abstract classes by subclassing (i.e., inheriting) from abc.ABC:-
import abc
# MyABC is inherited from abc.ABC
class MyABC(abc.ABC):
@abc.abstractmethod
def some_method(self):
print("Inside abstract class myABC")
# FromMyABC is inherited from MyABC
class FromMyABC(MyABC):
def some_method(self):
print("Inside derived class FromMyABC")
# Create an object of the derived class FromMyABC
an_object = FromMyABC()
an_object.some_method()
Now another point to note is that you may always call an abstract method of our class MyABC from our derived class, i.e., FromMyABC, using the super() keyword as shown in the following code. (A single line of code has been added. As a result of adding this line, the some_method() abstract method of MyABC class is also called):
The script is given on page 366 of the book
import abc
# MyABC is inherited from abc.ABC
class MyABC(abc.ABC):
@abc.abstractmethod
def some_method(self):
print("Inside abstract class myABC")
#FromMyABC is inherited from MyABC
class FromMyABC(MyABC):
def some_method(self):
super().some_method()
print("Inside derived class FromMyABC")
# Create an object of the derived class FromMyABC
an_object = FromMyABC()
an_object.some_method()